๐ Spring Logging ๊ฐ์
โ
Log๋ ์ ํ๋ฆฌ์ผ์ด์
์ ๋์์ ์ถ์ ํ๊ณ ๋ฌธ์ ๋ฅผ ์ง๋จํ๋ ์ค์ํ ๊ธฐ๋ฅ
โ
Spring์ ๋ค์ํ ๋ก๊น
ํ๋ ์์ํฌ๋ฅผ ์ง์ํ๋ฉฐ, ๊ธฐ๋ณธ์ ์ผ๋ก SLF4J๋ฅผ ๊ถ์ฅ
๐ SLF4J(Simple Logging Facade For Java)
โ SLF4J๋ ๋ค์ํ ๊ตฌํ์ฒด(Logback, Log4j ๋ฑ)๊ณผ ์ฐ๋ ๊ฐ๋ฅํ ์ถ์ํ ๊ณ์ธต ์ ๊ณต
์ฃผ์ ํน์ง
๐น ๋ก๊น ๊ตฌํ์ฒด์ ๋ํ ์์กด์ฑ ์ ๊ฑฐ
- ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํด ์ค์ ๋ก๊ทธ ์ฒ๋ฆฌ ๊ตฌํ์ ์์
- ๋ฐํ์์ ์ํ๋ ๋ก๊น ํ๋ ์์ํฌ๋ก ๋ณ๊ฒฝ ๊ฐ๋ฅ
๐น ์ ์ฐํ ๋ก๊น ํ๋ ์์ํฌ ๋ณ๊ฒฝ ๊ฐ๋ฅ
- ์ค์ ๋ง ๋ณ๊ฒฝํ๋ฉด SLF4J API๋ง ์ฌ์ฉํด ๊ตฌํ์ฒด ๋ณ๊ฒฝ ๊ฐ๋ฅ
๐น ๋ฐ์ธ๋ฉ ๊ฐ๋ ์ ์ฌ์ฉํด ๋ก๊น ํ๋ ์์ํฌ ์ฐ๊ฒฐ
- ๋ก๊น ๊ตฌํ์ฒด์ ๋ฐ์ธ๋ฉ์ ์ถ๊ฐํด ๋ก๊ทธ ์ถ๋ ฅ
๊ตฌ์กฐ
๐น SLF4J API
- ๋ก๊น ์ ์ํ ๊ณตํต ์ธํฐํ์ด์ค
๐น SLF4J ๋ฐ์ธ๋ฉ
- SLF4J์ ์ค์ ๋ก๊น ๊ตฌํ์ฒด๋ฅผ ์ฐ๊ฒฐํ๋ ์ญํ
๐น ๋ก๊น ๊ตฌํ์ฒด
- ์ค์ ๋ก๊ทธ๋ฅผ ์ถ๋ ฅํ๋ ์์ง
์ฃผ์ ์ธํฐํ์ด์ค
Logger.java
package org.slf4j;
public interface Logger {
String getName();
boolean isTraceEnabled();
void trace(String msg);
void trace(String format, Object arg);
void trace(String format, Object arg1, Object arg2);
void trace(String format, Object... arguments);
void trace(String msg, Throwable t);
boolean isDebugEnabled();
void debug(String msg);
...
boolean isInfoEnabled();
void info(String msg);
...
boolean isWarnEnabled();
void warn(String msg);
...
boolean isErrorEnabled();
void error(String msg);
...
}์ ์ธํฐํ์ด์ค๋ ๋ก๊น
์ ์ํ ๊ธฐ๋ณธ ๋ฉ์๋๋ค์ ์ ์ํ๋ฉฐ, ๋ชจ๋ ๊ตฌํ์ฒด๋ ์ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ผ ํ๋ค.
ํฉํ ๋ฆฌ ํจํด์ผ๋ก ์ค์ Logger ๊ตฌํ์ฒด๊ฐ ์์ฑ๋๋ค.
๐ Log Level
โ
์ ํ๋ฆฌ์ผ์ด์
์์ ๋ก๊น
ํ ๋ฉ์์ง์ ์ค์๋๋ฅผ ๋ํ๋ด๋ ์ค์
โ
ํ์ํ์ง ์์ ๋ก๊ทธ๋ฅผ ์ค์ด๊ณ , ์ค์ํ ์ ๋ณด๋ฅผ ์ฝ๊ฒ ํ์ธ ๊ฐ๋ฅ
| ๋ก๊ทธ ๋ ๋ฒจ | ์ค๋ช | ์ฌ์ฉ ์์ |
|---|---|---|
| TRACE | ๊ฐ์ฅ ์์ธํ ๋ก๊ทธ, ๋๋ฒ๊น ์ฉ | ๋ด๋ถ ํ๋ฆ ์ถ์ (์ ์ฌ์ฉ๋์ง ์์) |
| DEBUG | ๋๋ฒ๊น ์ ์ํ ์์ธ ๋ก๊ทธ | ๊ฐ๋ฐ ์ ์ฝ๋ ํ๋ฆ ํ์ธ |
| INFO | ์ผ๋ฐ์ ์ธ ์คํ ์ ๋ณด | ์๋น์ค ์คํ, ์ฃผ์ ์ด๋ฒคํธ ๋ก๊น |
| WARN | ์ ์ฌ์ ์ธ ๋ฌธ์ ๊ฒฝ๊ณ | ์ฑ๋ฅ ์ ํ, ์์์น ๋ชปํ ๋์ |
| ERROR | ์ฌ๊ฐํ ์ค๋ฅ ๋ก๊ทธ | ์์ธ ๋ฐ์, ์์คํ ์ฅ์ |
๐น Spring Boot์ ๊ธฐ๋ณธ ๋ก๊ทธ ๋ ๋ฒจ์ INFO
๐น application.properties์์ ๋ก๊ทธ ๋ ๋ฒจ ์ค์ ๊ฐ๋ฅ
logging.level.root=DEBUG๐น ์ด์ ํ๊ฒฝ์์๋ INFO, ๊ฐ๋ฐ ํ๊ฒฝ์์๋ DEBUG ๊ถ์ฅ
๐ SLF4J์ ๊ตฌํ์ฒด
Logback
โ Spring Boot์ ๊ธฐ๋ณธ ๋ก๊น ๊ตฌํ์ฒด
๐น ํน์ง
- SLF4J์ ๊ณต์ ๊ตฌํ์ฒด
- XML ๋ฐ Groovy ๊ธฐ๋ฐ ์ค์ ๊ฐ๋ฅ
- RollingFileAppender๋ฅผ ํตํ ๋ก๊ทธ ๋กค๋ง ์ง์
Log4j2
โ
Log4j2๋ Log4j์ ๊ฐ์ ๋ ๋ฒ์ ์ผ๋ก, ์ฑ๋ฅ๊ณผ ํ์ฅ์ฑ์ด ๋ฐ์ด๋ ๋ก๊น
ํ๋ ์์ํฌ
โ
Logback๋ณด๋ค ์ฑ๋ฅ์ด ๋ ์ข๊ณ ๋น๋๊ธฐ ๋ก๊น
๊ฐ๋ฅ
๐น ํน์ง
- ๋น๋๊ธฐ ๋ก๊น ์ง์
- JSON, YAML, XML ์ค์ ํ์ผ ์ง์
- Garbage-Free(GC-Free) ๋ก๊น ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ ์ต์ ํ
๐น Spring Boot์์ Log4j2 ์ฌ์ฉ
๊ธฐ์กด Logback์ ์ ์ธ์ํค๊ณ , Log4j2๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค.
dependencies {
implementation "org.springframework.boot:spring-boot-starter-log4j2"
modules {
module("org.springframework.boot:spring-boot-starter-logging") {
replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback")
}
}
}๐ MDC(Mapped Diagnostic Context)
โ
๊ฐ ์์ฒญ๋ง๋ค ๊ด๋ จ๋ ๋ฐ์ดํฐ๋ฅผ ์ ์งํ๊ณ ๋ก๊ทธ์ ์๋์ผ๋ก ํฌํจํ ์ ์๋๋ก ๋์์ฃผ๋ Key-Value ์ ์ฅ์
โ
๋ด๋ถ์ ์ผ๋ก ThreadLocal์ ์ฌ์ฉํด ๊ฐ ์ฐ๋ ๋๋ง๋ค ์ ์ฅ์๋ฅผ ์ ์ง
ํน์ง
๐น ์์ฒญ ๋จ์ ๋ก๊ทธ ์ถ์ ๊ฐ๋ฅ
- ๊ฐ์ ์์ฒญ์์ ๋ฐ์ํ๋ ๋ชจ๋ ๋ก๊ทธ์ ๊ณตํต๋ ์ ๋ณด๋ฅผ ์๋์ผ๋ก ์ถ๊ฐ ๊ฐ๋ฅ
๐น key-value ๊ธฐ๋ฐ ์ ์ฅ
๐น ๋ก๊ทธ ์๋ ์ถ๋ ฅ
- MDC์ ์ ์ฅ๋ ๊ฐ๋ค์ ๋ก๊น ๊ตฌํ์ฒด ์ค์ ์ ํตํด ์๋์ผ๋ก ๋ก๊ทธ์ ํฌํจ ๊ฐ๋ฅ
๐ Structured Log
โ
๋จ์ํ ๋ฌธ์์ด ํํ๊ฐ ์๋ JSON์ด๋ key-value ํ์์ผ๋ก ๊ธฐ๋กํ๋ ๋ก๊น
๊ธฐ๋ฒ
โ
๋ก๊ทธ ๋ถ์, ๊ฒ์, ํํฐ๋ง์ ์ต์ ํ๋จ (ELK, Grafana์ ์ฐ๋ ๊ฐ๋ฅ)
์ฅ์
๐น ๊ฒ์ ๋ฐ ํํฐ๋ง ์ฉ์ด
๐น ๋ก๊ทธ ๋ฐ์ดํฐ ์๊ฐํ ๊ฐ๋ฅ
- ELK(Elasticsearch, Logstash, Kibana), Grafana ๋ฑ์ ๋ก๊ทธ ๋ถ์ ๋๊ตฌ์ ์ฝ๊ฒ ์ฐ๋ ๊ฐ๋ฅ
- ๋ก๊ทธ ๋ฐ์ดํฐ๋ฅผ ์ฐจํธ, ๊ทธ๋ํ ํํ๋ก ๋ถ์ ๊ฐ๋ฅ
๐น JSON ํฌ๋งท์ ์ฌ์ฉํ ๋จธ์ ๋ฌ๋ ๋ถ์ ๊ฐ๋ฅ
- ๋น ๋ฐ์ดํฐ ๋ถ์์ด๋ ๋จธ์ ๋ฌ๋ ๋ชจ๋ธ ํ์ต์ ํ์ฉ ๊ฐ๋ฅ
๐น ๊ฒ์ ๋ฐ ํํฐ๋ง ์ฉ์ด
- MSA์์ ๊ฐ ์๋น์ค์ ๋ก๊ทธ๋ฅผ ๊ณตํต๋ JSON ํฌ๋งท์ผ๋ก ํตํฉ ๊ฐ๋ฅ
๐น ๊ฒ์ ๋ฐ ํํฐ๋ง ์ฉ์ด
Format
โ ๊ธฐ๋ณธ์ ์ผ๋ก ์ถ๋ ฅ๋๋ JSON ๋ก๊ทธ๋, ์ค์ ๋ ํฌ๋งท์ ๋ง๊ฒ MDC ๊ฐ์ ํ์ฉํ์ฌ ์๋์ผ๋ก ์์ฑ
๐น ECS
ElasticSearch์ ํธํ๋๋ ํ์ค ๋ก๊ทธ ํ์
์ค์
logging.structured.format.console=ecs
logging.structured.format.file=ecs์์
{
"@timestamp": "2025-02-25T12:00:00.123Z",
"log.level": "INFO",
"message": "User login successful",
"event.category": "authentication",
"event.action": "user_login",
"user.id": "567",
"user.name": "john_doe",
"http.request.method": "POST",
"http.response.status_code": 200
}๐น GELF
Graylog์ ์ต์ ํ๋ ๋ก๊ทธ ํ์
์ค์
logging.structured.format.console=gelf
logging.structured.format.file=gelf์์
{
"version": "1.1",
"host": "server-1",
"short_message": "User login successful",
"full_message": "User john_doe logged in from IP 192.168.1.100",
"timestamp": 1708886400.123,
"level": 6,
"_user_id": "567",
"_ip_address": "192.168.1.100",
"_http_method": "POST"
}๐น Logstash
Logstash์์ ์์งํ๊ณ ์ฒ๋ฆฌํ๊ธฐ ์ต์ ํ๋ ๋ก๊ทธ ํ์
logging.structured.format.console=logstash
logging.structured.format.file=logstash์์
{
"timestamp": "2025-02-25T12:00:00.123Z",
"level": "INFO",
"message": "User login successful",
"service": "UserService",
"user_id": "567",
"ip_address": "192.168.1.100",
"http_method": "POST"
}๐ ๊ถ๊ธํ ์
Structured Logging์ ์ด๋ป๊ฒ ํ์ฉํ ๊น?
์๋๋ ๊ฐ์ธ์ ์ธ ์๊ฒฌ์ ๋๋ค.
๋จผ์ , Logging์ ๋ํ ์์ง๋์ ํต์ฌ ๋ก์ง ๋ถ๋ฆฌ๋ฅผ ์ํด ์๋์ ๊ฐ์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ ๊ฒ ๊ฐ๋ค.
- Spring Filter ์ด์ฉ
- Spring AOP์์ ์๋ ์ค์
์์ฒญ์ด ์์๋ ๋๋ถํฐ ๋๋ ๋๊น์ง ๊ฐ์ ธ์ผํ ๊ณตํต ํ๋๋ Filter์์๋ถํฐ ์ฒ๋ฆฌํ๊ณ ,
ํน์ Layer์ ๋ฉ์๋๋ฅผ ํธ์ถํ ๋์ ๊ฐ์ด ๋ก๊น
๋ถ๊ฐ ๊ธฐ๋ฅ์ ๋ถ๋ฆฌํ ๋๋ AOP๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ ๊ฒ ๊ฐ๋ค.
user.id๋ฅผ filter๋ฅผ ํ์ฉํด ๊ณตํต ํ๋๋ก ์ถ๊ฐํด๋ณด์.
application.properties
logging.structured.format.console=logstashJsonLogFilter.java
@Component
public class JsonLogFilter implements Filter {
@Override
public void doFilter(final ServletRequest request, final ServletResponse response,
final FilterChain chain)
throws IOException, ServletException {
try {
String userId = "example userId";
MDC.put("user.id", userId);
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}log
{
"@timestamp":"2025-02-25T23:26:59.796577+09:00",
"@version":"1",
"message":"test log",
"logger_name":"com.example.javacicdpractice.member.MemberController",
"thread_name":"http-nio-8080-exec-1",
"level":"INFO",
"level_value":20000,
"user.id":"example userId"
}user.id๊ฐ ๋ก๊ทธ์ ์ ์์ ์ผ๋ก ์ถ๊ฐ๋์๋ค.
๐ ์ฐธ๊ณ
https://docs.spring.io/spring-boot/how-to/logging.html https://docs.spring.io/spring-boot/reference/features/logging.html#features.logging.log-levels